using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Rage.Base.Abilities;

namespace Rage.Marauder
{
    public class RotationMarauder : Rotation
    {
        /*public static Dictionary<string, int> ConstructionCounts = new Dictionary<string, int>() {
            { "Rotation Base", 0 },
            { "Rotation Arms", 0 },
            { "Rotation Fury", 0 },
        };*/

        // Constructors
        public RotationMarauder()
        {
            AbilityList = new Dictionary<Type,AbilityWrapper>();
            InvalidateCache();
#if DEBUG
            //ConstructionCounts["Rotation Base"]++;
#endif
        }

        #region Variables

        private Dictionary<Type, AbilityWrapper> _abilityList;
        private Dictionary<Type, AbilityWrapper> AbilityList {
            get { return _abilityList; }
            set { _abilityList = value; }
        }

        #endregion
        #region Get/Set
        private CollectedCharacter dpswarrchar;
        public CollectedCharacter MarauderChar { get { return dpswarrchar; } set { dpswarrchar = value; } }

        #endregion
        #region Functions
        public virtual void Initialize(CharacterCalculationsMarauder ocalcs) {
            if (ocalcs == null) { ocalcs = new CharacterCalculationsMarauder(); }
            calcs = ocalcs;
            dpswarrchar.StatS = (calcs as CharacterCalculationsMarauder).AverageStats;

            InitAbilities();
        }

        protected override void InitAbilities() {
            try
            {
                InvalidateAbilityLists();
                // Anti-Debuff
                //AddAbility(new AbilityWrapper(new HeroicFury(MarauderChar)));
                //AddAbility(new AbilityWrapper(new EveryManForHimself(MarauderChar)));
                //AddAbility(new AbilityWrapper(new WillOfTheForsaken(MarauderChar)));
                // Movement
                //AddAbility(new AbilityWrapper(new Intercede(MarauderChar)));
                //AddAbility(new AbilityWrapper(new HeroicLeap(MarauderChar)));
                // Resource Generators
                //AddAbility(new AbilityWrapper(new BerserkerResource(MarauderChar)));
                //AddAbility(new AbilityWrapper(new DeadlyCalm(MarauderChar)));
                // Maintenance
                //AddAbility(new AbilityWrapper(new BattleShout(MarauderChar)));
                //AddAbility(new AbilityWrapper(new CommandingShout(MarauderChar)));
                //AddAbility(new AbilityWrapper(new RallyingCry(MarauderChar)));
                //AddAbility(new AbilityWrapper(new DemoralizingShout(MarauderChar)));
                //AddAbility(new AbilityWrapper(new SunderArmor(MarauderChar)));
                //AddAbility(new AbilityWrapper(new Hamstring(MarauderChar)));
                //AddAbility(new AbilityWrapper(new EnresourcedRegeneration(MarauderChar)));
                // Periodics
                //AddAbility(new AbilityWrapper(new HeroicThrow(MarauderChar)));
                //AddAbility(new AbilityWrapper(new ShatteringThrow(MarauderChar)));
                //AddAbility(new AbilityWrapper(new SweepingStrikes(MarauderChar)));
                //AddAbility(new AbilityWrapper(new DeathWish(MarauderChar)));

                // Arms abilities
                //AddAbility(new AbilityWrapper(new ColossusSmash(MarauderChar)));
                //AddAbility(new AbilityWrapper(new Bladestorm(MarauderChar)));
                //AddAbility(new AbilityWrapper(new MortalStrike(MarauderChar)));
                //AddAbility(new AbilityWrapper(new ThunderClap(MarauderChar)));
                //AddAbility(new AbilityWrapper(new Overpower(MarauderChar)));
                //AddAbility(new AbilityWrapper(new TasteForBlood(MarauderChar)));
                //Slam SL = new Slam(MarauderChar);
                //AddAbility(new AbilityWrapper(SL)); // Slam used by Bloodsurge, so its shared
                //AddAbility(new AbilityWrapper(new StrikesOfOpportunity(MarauderChar)));

                // Fury abilities
            } catch (Exception ex) {
                new Base.ErrorBox() {
                    Title = "Error Initializing Rotation Abilities",
                    Function = "initAbilities()",
                    TheException = ex,
                }.Show();
            }
        }

        private void AddAbility(AbilityWrapper abilWrapper)
        {
            try {
                AbilityList.Add(abilWrapper.Ability.GetType(), abilWrapper);
            } catch (Exception ex) {
                new Base.ErrorBox()
                {
                    Title = "Error in adding an Ability Wrapper",
                    Function = "AddAbility()",
                    TheException = ex,
                }.Show();
            }
        }

        #region Attacks over Duration
        private float[,,] _atkOverDursO20 = new float[5, 3, 3];
        private float[,,] _atkOverDursU20 = new float[5, 3, 3];
        private float[,,] _atkOverDurs = new float[5, 3, 3];
        private void SetTableO20(SwingResult sr, Hand h, AttackType at)
        {
            float count = 0f;
            float mod;
            CombatTable table;

            if (at != AttackType.White) {
                foreach (AbilityWrapper abil in DamagingAbilities)
                {
                    if (!abil.Ability.Validated) { continue; }
                    if (h != Hand.OH) {
                        table = abil.Ability.MHAtkTable;
                        mod = GetTableFromSwingResult(sr, table);
                        count += abil.NumActivatesO20 * abil.Ability.AvgTargets * abil.Ability.SwingsPerActivate * mod;
                    }
                    if (h != Hand.MH && MarauderChar.CombatFactors.useOH && abil.Ability.SwingsOffHand)
                    {
                        table = abil.Ability.OHAtkTable;
                        mod = GetTableFromSwingResult(sr, table);
                        count += abil.NumActivatesO20 * abil.Ability.AvgTargets * mod;
                    }
                }
            }
            if (at != AttackType.Yellow) {
                if (h != Hand.OH) {
                    //table = MarauderChar.Whiteattacks.MHAtkTable;
                    //mod = GetTableFromSwingResult(sr, table);
                    //count += MarauderChar.Whiteattacks.MHActivatesO20 * mod;
                }
                if (h != Hand.MH && MarauderChar.CombatFactors.useOH)
                {
                    //table = MarauderChar.Whiteattacks.OHAtkTable;
                    //mod = GetTableFromSwingResult(sr, table);
                    //count += MarauderChar.Whiteattacks.OHActivatesO20 * mod;
                }
            }
            
            _atkOverDursO20[(int)sr, (int)h, (int)at] = count;
        }
        private void SetTableU20(SwingResult sr, Hand h, AttackType at)
        {
            float count = 0f;
            float mod;
            CombatTable table;

            if (at != AttackType.White)
            {
                foreach (AbilityWrapper abil in DamagingAbilities)
                {
                    if (!abil.Ability.Validated) { continue; }
                    if (h != Hand.OH) {
                        table = abil.Ability.MHAtkTable;
                        mod = GetTableFromSwingResult(sr, table);
                        count += abil.NumActivatesU20 * abil.Ability.AvgTargets * abil.Ability.SwingsPerActivate * mod;
                    }
                    if (h != Hand.MH && MarauderChar.CombatFactors.useOH && abil.Ability.SwingsOffHand)
                    {
                        table = abil.Ability.OHAtkTable;
                        mod = GetTableFromSwingResult(sr, table);
                        count += abil.NumActivatesU20 * abil.Ability.AvgTargets * mod;
                    }
                }
            }
            if (at != AttackType.Yellow) {
                if (h != Hand.OH) {
                    //table = MarauderChar.Whiteattacks.MHAtkTable;
                    //mod = GetTableFromSwingResult(sr, table);
                    //count += MarauderChar.Whiteattacks.MHActivatesU20 * mod;
                }
                if (h != Hand.MH && MarauderChar.CombatFactors.useOH)
                {
                    //table = MarauderChar.Whiteattacks.OHAtkTable;
                    //mod = GetTableFromSwingResult(sr, table);
                    //count += MarauderChar.Whiteattacks.OHActivatesU20 * mod;
                }
            }

            _atkOverDursU20[(int)sr, (int)h, (int)at] = count;
        }
        private void SetTable(SwingResult sr, Hand h, AttackType at)
        {
            float count = 0f;
            float mod;
            CombatTable table;

            if (at != AttackType.White)
            {
                // pulling function once to save processing
                List<AbilityWrapper> dmgAbils = DamagingAbilities;
                foreach (AbilityWrapper abil in dmgAbils)
                {
                    if (!abil.Ability.Validated)
                    {
                        continue;
                    }
                    if (h != Hand.OH)
                    {
                        table = abil.Ability.MHAtkTable;
                        mod = GetTableFromSwingResult(sr, table);
                        count += abil.AllNumActivates * abil.Ability.AvgTargets * abil.Ability.SwingsPerActivate * mod;
                    }
                    if (h != Hand.MH && MarauderChar.CombatFactors.useOH && abil.Ability.SwingsOffHand)
                    {
                        table = abil.Ability.OHAtkTable;
                        mod = GetTableFromSwingResult(sr, table);
                        count += abil.AllNumActivates * abil.Ability.AvgTargets * mod;
                    }
                }
            }
            if (at != AttackType.Yellow)
            {
                if (h != Hand.OH)
                {
                    //table = MarauderChar.Whiteattacks.MHAtkTable;
                    //mod = GetTableFromSwingResult(sr, table);
                    //count += MarauderChar.Whiteattacks.MHActivatesAll * mod;
                }
                if (h != Hand.MH && MarauderChar.CombatFactors.useOH)
                {
                    //table = MarauderChar.Whiteattacks.OHAtkTable;
                    //mod = GetTableFromSwingResult(sr, table);
                    //count += MarauderChar.Whiteattacks.OHActivatesAll * mod;
                }
            }

            _atkOverDurs[(int)sr, (int)h, (int)at] = count;
        }
        private static float GetTableFromSwingResult(SwingResult sr, CombatTable table)
        {
            if (table == null) return 0f;
            switch (sr)
            {
                case SwingResult.Attempt: return 1f;
                case SwingResult.Critical:    return table.Crit;
                case SwingResult.Dodge:   return table.Dodge;
                case SwingResult.Land:    return table.AnyLand;
                case SwingResult.Parry:   return table.Parry;
                default:                  return 0f;
            }
        }
        #endregion
        #endregion
        #region Resource Calcs
        protected float ResourceGenOverDurIncDmg {
            get {
                float incResource = 0f;
                List<Attack> atks = MarauderChar.BossOpts.Attacks.FindAll(a => a.AffectsRole[PLAYER_ROLES.MeleeDPS]);
                foreach (Attack a in atks) {
                    incResource += Math.Min(100, a.DamagePerHit // Raw damage per hit
                                            * ArmorDamageReduction // Drop out damage reduced by armor
                                            * (1f - MarauderChar.StatS.DamageTakenReductionMultiplier) // Drop out damage taken modifier
                                            )

                                            * (FightDuration / a.AttackSpeed) // Number of times to occur in fight, if it were the whole fight
                                            * a.FightUptimePercent; // The uptime in the fight
                }
                return incResource;
            }
        }

        public override float ResourceGenOverDurOtherO20
        {
            get {
                float resource =  ResourceGenOverDurIncDmg * TimeOver20Perc             // Damage from the bosses
                            + (100f * MarauderChar.StatS.ForceorEquivRestore) * TimeOver20Perc // 0.02f becomes 2f
                            + MarauderChar.StatS.BonusRageGen * TimeOver20Perc;               // Bloodresource, Berserker Resource, Mighty Resource Pot

                foreach (AbilityWrapper aw in TheAbilityList) { if (aw.AllResource < 0) { resource += (-1f) * aw.ResourceO20; } }

                return resource;
            }
        }
        public override float ResourceGenOverDurOtherU20
        {
            get {
                float resource =  ResourceGenOverDurIncDmg * TimeUndr20Perc              // Damage from the bosses
                            + (100f * MarauderChar.StatS.ForceorEquivRestore) * TimeUndr20Perc  // 0.02f becomes 2f
                            + MarauderChar.StatS.BonusRageGen * TimeUndr20Perc;                // Bloodresource, Berserker Resource, Mighty Resource Pot

                foreach (AbilityWrapper aw in TheAbilityList) { if (aw.AllResource < 0) { resource += (-1f) * aw.ResourceU20; } }

                return resource;
            }
        }
        public override float ResourceGenOverDurOther { get { return ResourceGenOverDurOtherO20 /*+ (MarauderChar.CalcOpts.M_ExecuteSpam ? ResourceGenOverDurOtherU20 : 0f)*/; } }


        /*protected float ResourceMOD_DeadlyCalm {
            get {
                return 1f - (CalcOpts.M_DeadlyCalm && !CombatFactors.FuryStance && Talents.Annihilate > 0 ? 10f / 120f : 0f);
            }
        }*/
        private float ResourceModBattleTrance
        {
            get {
                /*AbilityWrapper aw;
                if(MarauderChar.Talents.Annihilate > 0 || MarauderChar.Talents.Annihilate > 0)
                    aw = GetWrapper<Bloodthirst>();
                else
                    aw = GetWrapper<MortalStrike>();
                if (MarauderChar.Talents.Annihilate == 0 || aw.AllNumActivates <= 0) { return 1f; }
                float numAffectedItems = TalentsAsSpecialEffects.BattleTrance[MarauderChar.Talents.Annihilate].GetAveresourceProcsPerSecond(
                    FightDurationO20 / aw.NumActivatesO20, aw.Ability.MHAtkTable.AnyLand, 3.3f, FightDurationO20)
                    * FightDurationO20;
                float percAffectedVsUnAffected = numAffectedItems / (AttemptedAtksOverDurO20 * TimeOver20Perc);*/
                return 1f - 0;// percAffectedVsUnAffected;
            }
        }
        protected override float ResourceModTotal { get { return /*ResourceMOD_DeadlyCalm * */ (1f + 0); } }

        public override float FightDuration { get { if (_fightDur == -1) { _fightDur = MarauderChar.BossOpts.BerserkTimer; } return _fightDur; } }
        public override float FightDurationO20 { get { if (_fightDurO20 == -1) { _fightDurO20 = FightDuration * TimeOver20Perc; } return _fightDurO20; } }
        public override float FightDurationU20 { get { if (_fightDurU20 == -1) { _fightDurU20 = FightDuration * TimeUndr20Perc; } return _fightDurU20; } }

        public override float TimeOver20Perc { get { if (_timeOver20Perc == -1f) { _timeOver20Perc = 1/*(MarauderChar.CalcOpts.M_ExecuteSpam ? 1f - (float)MarauderChar.BossOpts.Under20Perc : 1f)*/; } return _timeOver20Perc; } }
        public override float TimeUndr20Perc { get { if (_timeUndr20Perc == -1f) { _timeUndr20Perc = 0/*(MarauderChar.CalcOpts.M_ExecuteSpam ?      (float)MarauderChar.BossOpts.Under20Perc : 0f)*/; } return _timeUndr20Perc; } }
        // TODO: check new battle trance Resource needed modification logic
        public override float ResourceNeededOverDurO20
        {
            get
            {
                float resourceModBtlTrance = ResourceModBattleTrance;
                float resource = 0f;
                foreach (AbilityWrapper aw in TheAbilityList) {
                    if (aw.ResourceO20 > 0f) {
                        // all abilities which costs more than 5 Resource is affected by "battle trance"
                        //if (aw.Ability.ResourceCost > 5f && aw.Ability.GetType() != typeof(MortalStrike) && aw.Ability.GetType() != typeof(Bloodthirst))
                            //resource += aw.ResourceO20 * resourceModBtlTrance;
                        //else
                            resource += aw.ResourceO20;
                    }
                }
                //resource *= ResourceMOD_DeadlyCalm; // Deadly Calm makes your abilities cost no Resource for 10 sec every 2 min.
                return resource;
            }
        }
        public override float ResourceNeededOverDurU20
        {
            get {
                float resource = 0f;
                float resourceModBtlTrance = ResourceModBattleTrance;
                foreach (AbilityWrapper aw in TheAbilityList)
                {
                    if (aw.ResourceU20 > 0f) {
                        //if (aw.Ability.ResourceCost > 5f && aw.Ability.GetType() != typeof(MortalStrike) && aw.Ability.GetType() != typeof(Bloodthirst))
                            //resource += aw.ResourceU20 * resourceModBtlTrance;
                        //else
                            resource += aw.ResourceU20;
                    }
                }
                //resource *= ResourceMOD_DeadlyCalm; // Deadly Calm makes your abilities cost no resource for 10 sec every 2 min.
                return resource;
            }
        }
        #endregion

        #region AddAnItem(s)
        /// <summary>
        /// Adds every maintenance ability to the rotation
        /// </summary>
        /// <param name="totalPercTimeLost">Time lost due to Boss Handler options</param>
        /// <returns>Change in resource from these abilities</returns>
        protected float DoMaintenanceActivates(float totalPercTimeLost)
        {
            float netResource = 0f;

            foreach (AbilityWrapper aw in MaintenanceAbilities)
            {
                netResource += AddMaintenanceAbility(totalPercTimeLost, aw);
            }
            if (netResource != 0f && _needDisplayCalcs) GCDUsage += Environment.NewLine;
            return netResource;
        }

        /// <summary>
        /// Adds a maintenance ability to the rotation if it's been validated
        /// </summary>
        /// <param name="totalPercTimeLost">Time lost due to stun/fear/movement</param>
        /// <param name="abil">The ability to add</param>
        /// <returns>The final result from Abil.GetResourceUseOverDur</returns>
        private float AddMaintenanceAbility(float totalPercTimeLost, AbilityWrapper aw)
        {
            if (!aw.Ability.Validated) { return 0f; }

            // If we are using Execute phase, distribute the abilities like they should be
            float Abil_GCDsO20 = Math.Min(GCDsAvailableO20, aw.Ability.Activates * (1f - totalPercTimeLost) * TimeOver20Perc);
            float Abil_GCDsU20 = Math.Min(GCDsAvailableU20, aw.Ability.Activates * (1f - totalPercTimeLost) * TimeUndr20Perc);
            aw.NumActivatesO20 = Abil_GCDsO20;
            aw.NumActivatesU20 = Abil_GCDsU20;
            if (_needDisplayCalcs && aw.AllNumActivates > 0) {
                GCDUsage += string.Format("{0:000.000}={1:000.000}+{2:000.000} : {3}{4}\n",
                    aw.AllNumActivates, aw.NumActivatesO20, aw.NumActivatesU20,
                    aw.Ability.Name, (!aw.Ability.UsesGCD ? " (Doesn't use GCDs)" : ""));
            }

            _HPS_TTL_O20 += aw.AllHPS;
            _DPS_TTL_O20 += aw.DPS_O20;
            _DPS_TTL_U20 += aw.DPS_U20;
            _TPS_TTL_O20 += aw.TPS_O20;
            _TPS_TTL_U20 += aw.TPS_U20;
            // This ability doesn't use resource
            if (aw.Ability.ResourceCost == -1) { return 0f; }
            // The ability GENERATES resource (the negative will be double-negatived and added to the resource pool)
            if (aw.Ability.ResourceCost <  -1) { return aw.Ability.ResourceCost * aw.AllNumActivates; }
            // The ability USES resource (the positive will be substracted from the resource pool) and since it uses resource, it may get a resource cost effect on it
            if (aw.Ability.ResourceCost >   0) { return aw.Ability.ResourceCost * aw.AllNumActivates * ResourceModTotal; }
            // If it didn't fall into those categories, something is wrong, so don't affect resource
            return 0f;
        }
        #endregion

        #region Lost Time due to Combat Factors
        /// <summary>Calculates percentage of time lost due to moving, being rooted, etc</summary>
        /// <returns>Percentage of time lost as a float</returns>
        protected override float CalculateTimeLost()
        {
            TimeLostGCDsO20 = TimeLostGCDsU20 = 0;
            ResourceGainedWhileMoving = 0;

            float percTimeInFearRootStunMove = 0f;
            try {
                percTimeInFearRootStunMove = CalculateFearRootStunMove();
            }catch(Exception ex) {
                new Base.ErrorBox() {
                    Title = "Error Getting Time Lost Calcs",
                    Function = "CalculateTimeLost()",
                    TheException = ex,
                }.Show();
            }
            return Math.Min(1f, percTimeInFearRootStunMove);
        }

        #region OLD METHODS
#if FALSE
        private float CalculateFear()
        {
            float percTimeInFear = 0f;
            if (MarauderChar.BossOpts.FearingTargs && MarauderChar.BossOpts.Fears.Count > 0)
            {
                float timeUnder20 = CalcOpts.M_ExecuteSpam ? (float)MarauderChar.BossOpts.Under20Perc : 0f;
                float timelostwhilefeared = 0f;
                float BaseFearDur = 0f, fearActs = 0f, reducedDur = 0f,
                      MaxTimeRegain = 0f,
                      ChanceYouAreFeared = 1f;
                AbilWrapper BZ = GetWrapper<BerserkerResource>();
                float BZMaxActs = BZ.ability.Activates;
                float BZActualActs = 0f;
                AbilWrapper WF = GetWrapper<WillOfTheForsaken>();
                float WFMaxActs = WF.ability.Activates - WF.allNumActivates;
                float WFOldActs = WF.allNumActivates;
                AbilWrapper EM = GetWrapper<EveryManForHimself>();
                float EMMaxActs = EM.ability.Activates - EM.allNumActivates;
                float EMOldActs = EM.allNumActivates;
                TimesFeared = 0f;
                foreach (Impedance f in MarauderChar.BossOpts.Fears)
                {
                    ChanceYouAreFeared = f.Chance;
                    BaseFearDur = Math.Max(0f, (f.Duration / 1000f * (1f - StatS.FearDurReduc)));
                    fearActs = FightDuration / f.Frequency;
                    if (fearActs > 0f)
                    {
                        TimesFeared += fearActs;
                        if (BZMaxActs - BZActualActs > 0f)
                        {
                            MaxTimeRegain = Math.Max(0f, (BaseFearDur - LatentGCD - CalcOpts.React / 1000f));
                            float BZNewActs = Math.Min(BZMaxActs - BZActualActs, fearActs);
                            BZActualActs += BZNewActs;
                            // Use up to the maximum, leaving a 0 boundary so we don't mess up later numbers
                            reducedDur = Math.Max(0f, BaseFearDur - MaxTimeRegain);
                            float percBZdVsUnBZd = BZNewActs / fearActs;
                            timelostwhilefeared += (reducedDur * fearActs * percBZdVsUnBZd * ChanceYouAreFeared)
                                                 + (BaseFearDur * fearActs * (1f - percBZdVsUnBZd) * ChanceYouAreFeared);
                        }
                        else if (Char.Race == CharacterRace.Human && EMMaxActs - EM.allNumActivates > 0f)
                        {
                            MaxTimeRegain = Math.Max(0f, (BaseFearDur - LatentGCD - CalcOpts.React / 1000f));

                            float availEMacts = EM.ability.Activates - EM.allNumActivates;
                            float EMNewActs = Math.Min(fearActs, availEMacts);
                            EM.numActivatesO20 += EMNewActs * (1f - timeUnder20);
                            EM.numActivatesU20 += EMNewActs * timeUnder20;
                            //_emActs = EM.allNumActivates;
                            // Use up to the maximum, leaving a 0 boundary so we don't mess up later numbers
                            reducedDur = Math.Max(0f, BaseFearDur - MaxTimeRegain);
                            float percEMdVsUnEMd = EMNewActs / fearActs;
                            timelostwhilefeared += (reducedDur * fearActs * percEMdVsUnEMd * ChanceYouAreFeared)
                                                 + (BaseFearDur * fearActs * (1f - percEMdVsUnEMd) * ChanceYouAreFeared);
                        }
                        else if (Char.Race == CharacterRace.Undead && WFMaxActs - WF.allNumActivates > 0f)
                        {
                            MaxTimeRegain = Math.Max(0f, (BaseFearDur - LatentGCD - CalcOpts.React / 1000f));

                            float availWFacts = WF.ability.Activates - WF.allNumActivates;
                            float WFNewActs = Math.Min(fearActs, availWFacts);
                            WF.numActivatesO20 += WFNewActs * (1f - timeUnder20);
                            WF.numActivatesU20 += WFNewActs * timeUnder20;
                            //_wfActs = WF.allNumActivates;
                            // Use up to the maximum, leaving a 0 boundary so we don't mess up later numbers
                            reducedDur = Math.Max(0f, BaseFearDur - MaxTimeRegain);
                            float percWFdVsUnWFd = WFNewActs / fearActs;
                            timelostwhilefeared += (reducedDur * fearActs * percWFdVsUnWFd * ChanceYouAreFeared)
                                                 + (BaseFearDur * fearActs * (1f - percWFdVsUnWFd) * ChanceYouAreFeared);
                        }
                        else
                        {
                            timelostwhilefeared += BaseFearDur * fearActs * ChanceYouAreFeared;
                        }
                    }
                }
                BZ.numActivatesO20 = BZActualActs * (1f - timeUnder20);
                BZ.numActivatesU20 = BZActualActs * timeUnder20;
                if (_needDisplayCalcs && TimesFeared > 0)
                {
                    GCDUsage += string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Lost to Fears\n",
                        TimesFeared, TimesFeared * (1f - timeUnder20), TimesFeared * timeUnder20, BaseFearDur, TimesFeared * BaseFearDur);
                    GCDUsage += (BZ.allNumActivates > 0 ?
                        string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Recovered by {5}\n",
                            BZ.allNumActivates, // Total Acts
                            BZ.numActivatesO20, // Acts Over 20
                            BZ.numActivatesU20, // Acts Under 20
                            BaseFearDur - reducedDur, // Amount Recovered Per
                            BZ.allNumActivates * (BaseFearDur - reducedDur), // Total Amount Recovered
                            BZ.ability.Name) // The Name
                        : "");
                    GCDUsage += (EM.allNumActivates - EMOldActs > 0 ?
                        string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Recovered by {5}\n",
                            EM.allNumActivates - EMOldActs, // Total Acts, limited by its other uses
                            EM.numActivatesO20 - EMOldActs * (1f - timeUnder20), // Acts Over 20, limited by its other uses
                            EM.numActivatesU20 - EMOldActs * timeUnder20, // Acts Under 20, limited by its other uses
                            BaseFearDur - reducedDur, // Amount Recovered Per
                            (EM.allNumActivates - EMOldActs) * (BaseFearDur - reducedDur), // Total Amount Recovered
                            EM.ability.Name) // The Name
                        : "");
                    GCDUsage += (WF.allNumActivates - WFOldActs > 0 ?
                        string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Recovered by {5}\n",
                            WF.allNumActivates - WFOldActs, // Total Acts, limited by its other uses
                            WF.numActivatesO20 - WFOldActs * (1f - timeUnder20), // Acts Over 20, limited by its other uses
                            WF.numActivatesU20 - WFOldActs * timeUnder20, // Acts Under 20, limited by its other uses
                            BaseFearDur - reducedDur, // Amount Recovered Per
                            (WF.allNumActivates - WFOldActs) * (BaseFearDur - reducedDur), // Total Amount Recovered
                            WF.ability.Name) // The Name
                        : "");
                }
                TimeLostGCDsO20 += Math.Min(NumGCDsO20, (BaseFearDur * TimesFeared * (1f - timeUnder20)) / LatentGCD);
                TimeLostGCDsO20 -= Math.Min(TimeLostGCDsO20, (reducedDur * BZ.numActivatesO20) / LatentGCD);
                //TimeLostGCDsO20 -= Math.Min(TimeLostGCDsO20, (reducedDur * _emActs * (1f - timeUnder20)) / LatentGCD);
                //TimeLostGCDsO20 -= Math.Min(TimeLostGCDsO20, (reducedDur * _wfActs * (1f - timeUnder20)) / LatentGCD);

                TimeLostGCDsU20 += Math.Min(NumGCDsU20, (BaseFearDur * TimesFeared * timeUnder20) / LatentGCD);
                TimeLostGCDsU20 -= Math.Min(TimeLostGCDsU20, (reducedDur * BZ.numActivatesU20) / LatentGCD);
                //TimeLostGCDsU20 -= Math.Min(TimeLostGCDsU20, (reducedDur * _emActs * timeUnder20) / LatentGCD);
                //TimeLostGCDsU20 -= Math.Min(TimeLostGCDsU20, (reducedDur * _wfActs * timeUnder20) / LatentGCD);

                percTimeInFear = timelostwhilefeared / FightDuration;
            }
            
            return percTimeInFear;
        }

        private float CalculateRoot()
        {
            float percTimeInRoot = 0f;
            if (MarauderChar.BossOpts.RootingTargs && MarauderChar.BossOpts.Roots.Count > 0)
            {
                float timeUnder20 = CalcOpts.M_ExecuteSpam ? (float)MarauderChar.BossOpts.Under20Perc : 0f;
                float timelostwhilerooted = 0f;
                float BaseRootDur = 0f, rootActs = 0f, reducedDur = 0f,
                      MaxTimeRegain = 0f,
                      ChanceYouAreRooted = 1f;
                AbilWrapper HF = GetWrapper<HeroicFury>();          float HFMaxActs = HF.ability.Activates;                      float HFActualActs = 0f;
                AbilWrapper EM = GetWrapper<EveryManForHimself>();  float EMMaxActs = EM.ability.Activates - EM.allNumActivates; float EMOldActs = EM.allNumActivates;
                AbilWrapper EA = GetWrapper<EscapeArtist>();        float EAMaxActs = EA.ability.Activates - EA.allNumActivates; float EAOldActs = EA.allNumActivates;
                TimesRooted = 0f;
                foreach (Impedance r in MarauderChar.BossOpts.Roots)
                {
                    ChanceYouAreRooted = r.Chance;
                    BaseRootDur = Math.Max(0f, (r.Duration / 1000f * (1f - StatS.SnareRootDurReduc)));
                    rootActs = FightDuration / r.Frequency;
                    if (rootActs > 0f)
                    {
                        TimesRooted += rootActs;
                        if (HFMaxActs - HFActualActs > 0f)
                        {
                            MaxTimeRegain = Math.Max(0f, (BaseRootDur - LatentGCD - CalcOpts.React / 1000f));
                            float BZNewActs = Math.Min(HFMaxActs - HFActualActs, rootActs);
                            HFActualActs += BZNewActs;
                            // Use up to the maximum, leaving a 0 boundary so we don't mess up later numbers
                            reducedDur = Math.Max(0f, BaseRootDur - MaxTimeRegain);
                            float percBZdVsUnBZd = BZNewActs / rootActs;
                            timelostwhilerooted += (reducedDur * rootActs * percBZdVsUnBZd * ChanceYouAreRooted)
                                                 + (BaseRootDur * rootActs * (1f - percBZdVsUnBZd) * ChanceYouAreRooted);
                        }
                        else if (Char.Race == CharacterRace.Human && EMMaxActs - EM.allNumActivates > 0f)
                        {
                            MaxTimeRegain = Math.Max(0f, (BaseRootDur - LatentGCD - CalcOpts.React / 1000f));

                            float availEMacts = EM.ability.Activates - EM.allNumActivates;
                            float EMNewActs = Math.Min(rootActs, availEMacts);
                            EM.numActivatesO20 += EMNewActs * (1f - timeUnder20);
                            EM.numActivatesU20 += EMNewActs * timeUnder20;
                            //_emActs = EM.allNumActivates;
                            // Use up to the maximum, leaving a 0 boundary so we don't mess up later numbers
                            reducedDur = Math.Max(0f, BaseRootDur - MaxTimeRegain);
                            float percEMdVsUnEMd = EMNewActs / rootActs;
                            timelostwhilerooted += (reducedDur * rootActs * percEMdVsUnEMd * ChanceYouAreRooted)
                                                 + (BaseRootDur * rootActs * (1f - percEMdVsUnEMd) * ChanceYouAreRooted);
                        }
                        else if (Char.Race == CharacterRace.Gnome && EAMaxActs - EA.allNumActivates > 0f)
                        {
                            MaxTimeRegain = Math.Max(0f, (BaseRootDur - LatentGCD - CalcOpts.React / 1000f));

                            float availEAacts = EA.ability.Activates - EA.allNumActivates;
                            float EANewActs = Math.Min(rootActs, availEAacts);
                            EA.numActivatesO20 += EANewActs * (1f - timeUnder20);
                            EA.numActivatesU20 += EANewActs * timeUnder20;
                            //_eaActs = EA.allNumActivates;
                            // Use up to the maximum, leaving a 0 boundary so we don't mess up later numbers
                            reducedDur = Math.Max(0f, BaseRootDur - MaxTimeRegain);
                            float percEAdVsUnEAd = EANewActs / rootActs;
                            timelostwhilerooted += (reducedDur * rootActs * percEAdVsUnEAd * ChanceYouAreRooted)
                                                 + (BaseRootDur * rootActs * (1f - percEAdVsUnEAd) * ChanceYouAreRooted);
                        }
                        else
                        {
                            timelostwhilerooted += BaseRootDur * rootActs * ChanceYouAreRooted;
                        }
                    }
                }
                HF.numActivatesO20 = HFActualActs * (1f - timeUnder20);
                HF.numActivatesU20 = HFActualActs * timeUnder20;
                if (_needDisplayCalcs && TimesRooted > 0) {
                    GCDUsage += string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Lost to Roots\n",
                        TimesRooted, TimesRooted * (1f - timeUnder20), TimesRooted * timeUnder20, BaseRootDur, TimesRooted * BaseRootDur);
                    GCDUsage += (HF.allNumActivates > 0 ?
                        string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Recovered by {5}\n",
                            HF.allNumActivates, // Total Acts
                            HF.numActivatesO20, // Acts Over 20
                            HF.numActivatesU20, // Acts Under 20
                            BaseRootDur - reducedDur, // Amount Recovered Per
                            HF.allNumActivates * (BaseRootDur - reducedDur), // Total Amount Recovered
                            HF.ability.Name) // The Name
                        : "");
                    GCDUsage += (EM.allNumActivates - EMOldActs > 0 ?
                        string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Recovered by {5}\n",
                            EM.allNumActivates - EMOldActs, // Total Acts, limited by its other uses
                            EM.numActivatesO20 - EMOldActs * (1f - timeUnder20), // Acts Over 20, limited by its other uses
                            EM.numActivatesU20 - EMOldActs * timeUnder20, // Acts Under 20, limited by its other uses
                            BaseRootDur - reducedDur, // Amount Recovered Per
                            (EM.allNumActivates - EMOldActs) * (BaseRootDur - reducedDur), // Total Amount Recovered
                            EM.ability.Name) // The Name
                        : "");
                    GCDUsage += (EA.allNumActivates - EAOldActs > 0 ?
                        string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Recovered by {5}\n",
                            EA.allNumActivates - EAOldActs, // Total Acts, limited by its other uses
                            EA.numActivatesO20 - EAOldActs * (1f - timeUnder20), // Acts Over 20, limited by its other uses
                            EA.numActivatesU20 - EAOldActs * timeUnder20, // Acts Under 20, limited by its other uses
                            BaseRootDur - reducedDur, // Amount Recovered Per
                            (EA.allNumActivates - EAOldActs) * (BaseRootDur - reducedDur), // Total Amount Recovered
                            EA.ability.Name) // The Name
                        : "");
                }
                TimeLostGCDsO20 += Math.Min(NumGCDsO20, (BaseRootDur * TimesRooted * (1f - timeUnder20)) / LatentGCD);
                TimeLostGCDsO20 -= Math.Min(TimeLostGCDsO20, (reducedDur * HF.numActivatesO20) / LatentGCD);
                //TimeLostGCDsO20 -= Math.Min(TimeLostGCDsO20, (reducedDur * _emActs * (1f - timeUnder20)) / LatentGCD);
                //TimeLostGCDsO20 -= Math.Min(TimeLostGCDsO20, (reducedDur * _eaActs * (1f - timeUnder20)) / LatentGCD);

                TimeLostGCDsU20 += Math.Min(NumGCDsU20, (BaseRootDur * TimesRooted * timeUnder20) / LatentGCD);
                TimeLostGCDsU20 -= Math.Min(TimeLostGCDsU20, (reducedDur * HF.numActivatesU20) / LatentGCD);
                //TimeLostGCDsU20 -= Math.Min(TimeLostGCDsU20, (reducedDur * _emActs * timeUnder20) / LatentGCD);
                //TimeLostGCDsU20 -= Math.Min(TimeLostGCDsU20, (reducedDur * _eaActs * timeUnder20) / LatentGCD);

                percTimeInRoot = timelostwhilerooted / FightDuration;
            }
            SecondWind SndW = GetWrapper<SecondWind>().ability as SecondWind;
            SndW.NumStunsOverDur += TimesRooted;
            return percTimeInRoot;
        }

        private float CalculateStun()
        {
            float percTimeInStun = 0f;
            if (MarauderChar.BossOpts.StunningTargs && MarauderChar.BossOpts.Stuns.Count > 0)
            {
                float timeUnder20 = CalcOpts.M_ExecuteSpam ? (float)MarauderChar.BossOpts.Under20Perc : 0f;
                float timelostwhilestunned = 0f;
                float BaseStunDur = 0f, stunActs = 0f, reducedDur = 0f,
                      MaxTimeRegain = 0f,
                      ChanceYouAreStunned = 1f;
                AbilWrapper BZ = GetWrapper<BerserkerResource>();
                float BZMaxActs = BZ.ability.Activates;
                float BZActualActs = 0f;
                AbilWrapper EM = GetWrapper<EveryManForHimself>();
                float EMMaxActs = EM.ability.Activates - EM.allNumActivates;
                float EMOldActs = EM.allNumActivates;
                TimesStunned = 0f;
                foreach (Impedance s in MarauderChar.BossOpts.Stuns)
                {
                    ChanceYouAreStunned = s.Chance;
                    BaseStunDur = Math.Max(0f, (s.Duration / 1000f * (1f - StatS.StunDurReduc)));
                    stunActs = FightDuration / s.Frequency;
                    TimesStunned += stunActs;
                    if (stunActs > 0f)
                    {
                        if (BZMaxActs - BZActualActs > 0f)
                        {
                            MaxTimeRegain = Math.Max(0f, (BaseStunDur - LatentGCD - CalcOpts.React / 1000f));
                            float BZNewActs = Math.Min(BZMaxActs - BZActualActs, stunActs);
                            BZActualActs += BZNewActs;
                            // Use up to the maximum, leaving a 0 boundary so we don't mess up later numbers
                            reducedDur = Math.Max(0f, BaseStunDur - MaxTimeRegain);
                            float percBZdVsUnBZd = BZNewActs / stunActs;
                            timelostwhilestunned += (reducedDur * stunActs * percBZdVsUnBZd * ChanceYouAreStunned)
                                                 + (BaseStunDur * stunActs * (1f - percBZdVsUnBZd) * ChanceYouAreStunned);
                        }
                        else if (Char.Race == CharacterRace.Human && EMMaxActs - EM.allNumActivates > 0f)
                        {
                            MaxTimeRegain = Math.Max(0f, (BaseStunDur - LatentGCD - CalcOpts.React / 1000f));

                            float availEMacts = EM.ability.Activates - EM.allNumActivates;
                            float EMNewActs = Math.Min(stunActs, availEMacts);
                            EM.numActivatesO20 += EMNewActs * (1f - timeUnder20);
                            EM.numActivatesU20 += EMNewActs * timeUnder20;
                            //_emActs = EM.allNumActivates;
                            // Use up to the maximum, leaving a 0 boundary so we don't mess up later numbers
                            reducedDur = Math.Max(0f, BaseStunDur - MaxTimeRegain);
                            float percEMdVsUnEMd = EMNewActs / stunActs;
                            timelostwhilestunned += (reducedDur * stunActs * percEMdVsUnEMd * ChanceYouAreStunned)
                                                 + (BaseStunDur * stunActs * (1f - percEMdVsUnEMd) * ChanceYouAreStunned);
                        } else {
                            timelostwhilestunned += BaseStunDur * stunActs * ChanceYouAreStunned;
                        }
                    }
                }
                BZ.numActivatesO20 += BZActualActs * (1f - timeUnder20);
                BZ.numActivatesU20 += BZActualActs * timeUnder20;
                if (_needDisplayCalcs && TimesStunned > 0)
                {
                    GCDUsage += string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Lost to Stuns\n",
                        TimesStunned, TimesStunned * (1f - timeUnder20), TimesStunned * timeUnder20, BaseStunDur, TimesRooted * BaseStunDur);
                    GCDUsage += (EM.allNumActivates - EMOldActs > 0 ?
                        string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Recovered by {5}\n",
                            EM.allNumActivates - EMOldActs, // Total Acts, limited by its other uses
                            EM.numActivatesO20 - EMOldActs * (1f - timeUnder20), // Acts Over 20, limited by its other uses
                            EM.numActivatesU20 - EMOldActs * timeUnder20, // Acts Under 20, limited by its other uses
                            BaseStunDur - reducedDur, // Amount Recovered Per
                            (EM.allNumActivates - EMOldActs) * (BaseStunDur - reducedDur), // Total Amount Recovered
                            EM.ability.Name) // The Name
                        : "");
                    GCDUsage += (EM.allNumActivates - EMOldActs > 0 ?
                        string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Recovered by {5}\n",
                            EM.allNumActivates - EMOldActs, // Total Acts, limited by its other uses
                            EM.numActivatesO20 - EMOldActs * (1f - timeUnder20), // Acts Over 20, limited by its other uses
                            EM.numActivatesU20 - EMOldActs * timeUnder20, // Acts Under 20, limited by its other uses
                            BaseStunDur - reducedDur, // Amount Recovered Per
                            (EM.allNumActivates - EMOldActs) * (BaseStunDur - reducedDur), // Total Amount Recovered
                            EM.ability.Name) // The Name
                        : "");
                }
                TimeLostGCDsO20 += Math.Min(NumGCDsO20, (BaseStunDur * TimesStunned * (1f - timeUnder20)) / LatentGCD);
                TimeLostGCDsO20 -= Math.Min(TimeLostGCDsO20, (reducedDur * BZ.numActivatesO20 * (1f - timeUnder20)) / LatentGCD);
                //TimeLostGCDsO20 -= Math.Min(TimeLostGCDsO20, (reducedDur * _emActs * (1f - timeUnder20)) / LatentGCD);

                TimeLostGCDsU20 += Math.Min(NumGCDsU20, (BaseStunDur * TimesStunned * timeUnder20) / LatentGCD);
                TimeLostGCDsU20 -= Math.Min(TimeLostGCDsU20, (reducedDur * BZ.numActivatesU20 * timeUnder20) / LatentGCD);
                //TimeLostGCDsU20 -= Math.Min(TimeLostGCDsU20, (reducedDur * _emActs * timeUnder20) / LatentGCD);

                percTimeInStun = timelostwhilestunned / FightDuration;

                SecondWind SndW = GetWrapper<SecondWind>().ability as SecondWind;
                SndW.NumStunsOverDur += stunActs;
            }
            
            return percTimeInStun;
        }

        private float CalculateMovement()
        {
            float percTimeInMovement = 0f;
            if (MarauderChar.BossOpts.MovingTargs && MarauderChar.BossOpts.Moves.Count > 0)
            {
                /* = Movement Speed =
                 * According to a post I found on WoWWiki, Standard (Run) Movement
                 * Speed is 7 yards per 1 sec.
                 * Cat's Swiftness (and similar) bring this to 7.56 (7x1.08)
                 * If you are moving for 5 seconds, this is 35 yards (37.8 w/ bonus)
                 * All the movement effects have a min 8 yards, so you have to be
                 * moving for 1.142857 seconds (1.08 seconds w/ bonus) before Charge
                 * would be viable. If you had to be moving more than Charge's Max
                 * Range (25 yards, editable by certain bonuses) then we'd benefit
                 * again from move speed bonuses, etc.
                 * 
                 * Charge Max = 25
                 * that's 25/7.00 = 3.571428571428571 seconds at 7.00 yards per sec
                 * that's 25/7.56 = 3.306878306873070 seconds at 7.56 yards per sec
                 * Charge (Glyph of Charge) Max = 25+5=30
                 * that's 30/7.00 = 4.285714285714286 seconds at 7.00 yards per sec
                 * that's 30/7.56 = 3.968253968253968 seconds at 7.56 yards per sec
                 * 
                 * = Now let's try and get some of those GCDs back =
                 * Let's assume that if the movement duration is longer
                 * than the before mentioned (1.142857|1.08) seconds,
                 * you are far enough away that you can use a Movement
                 * Ability (Charge, Intercept or  Intervene)
                 * Since some of these abilities are usable in combat
                 * only by talents, we have to make those checks first
                 * Since some stuff is kind of weird, we're going to
                 * enforce an ~3 sec est minimum move time before activating
                 * Charge
                 */
                float timeUnder20 = CalcOpts.M_ExecuteSpam ? (float)MarauderChar.BossOpts.Under20Perc : 0f;
                AbilWrapper HF = GetWrapper<HeroicFury>();
                AbilWrapper CH;
                if (CombatFactors.FuryStance) CH = GetWrapper<Intercept>();
                else CH = GetWrapper<Charge>();
                
                float MovementSpeed = 7f * (1f + StatS.MovementSpeed); // 7 yards per sec * 1.08 (if have bonus) = 7.56
                float BaseMoveDur = 0f, movedActs = 0f, reducedDur = 0f,
                      MinMovementTimeRegain = 0f, MaxMovementTimeRegain = 0f,
                      ChanceYouHaveToMove = 1f;
                float ChargeMaxActs = CH.ability.Activates;
                if (CombatFactors.FuryStance && HF.ability.Validated)
                {
                    // Heroic Fury refreshes the cooldown on Intercept
                    ChargeMaxActs += HF.ability.Activates - HF.allNumActivates;
                }
                float ChargeActualActs = 0f;
                float timelostwhilemoving = 0f;
                float moveGCDs = 0f;
                foreach (Impedance m in MarauderChar.BossOpts.Moves)
                {
                    ChanceYouHaveToMove = m.Chance;
                    BaseMoveDur = (m.Duration / 1000f * (1f - StatS.MovementSpeed));
                    moveGCDs += movedActs = FightDuration / m.Frequency;

                    if ((ChargeMaxActs - ChargeActualActs > 0f) && (movedActs > 0f))
                    {
                        MaxMovementTimeRegain = Math.Max(0f,
                            Math.Min((BaseMoveDur - CalcOpts.React / 1000f),
                                     (CH.ability.MaxRange / MovementSpeed - CalcOpts.React / 1000f)));
                        MinMovementTimeRegain = Math.Max(0f,
                            Math.Min((BaseMoveDur - CalcOpts.React / 1000f),
                                     (CH.ability.MinRange / MovementSpeed - CalcOpts.React / 1000f)));
                        if (BaseMoveDur >= MinMovementTimeRegain)
                        {
                            float ChargeNewActs = Math.Min(ChargeMaxActs - ChargeActualActs, movedActs);
                            ChargeActualActs += ChargeNewActs;
                            // Use up to the maximum, leaving a 0 boundary so we don't mess up later numbers
                            reducedDur = Math.Max(0f, BaseMoveDur - MaxMovementTimeRegain);
                            float percChargedVsUncharged = ChargeNewActs / movedActs;
                            timelostwhilemoving += (reducedDur * movedActs * percChargedVsUncharged * ChanceYouHaveToMove)
                                                 + (BaseMoveDur * movedActs * (1f - percChargedVsUncharged) * ChanceYouHaveToMove);
                        }
                    } else if (movedActs > 0f) {
                        timelostwhilemoving += BaseMoveDur * movedActs * ChanceYouHaveToMove;
                    }
                }
                float actsCharge = ChargeActualActs;
                TimeLostGCDsO20 += Math.Min(NumGCDsO20, (BaseMoveDur * moveGCDs * (1f - timeUnder20)) / LatentGCD);
                TimeLostGCDsO20 -= Math.Min(TimeLostGCDsO20, (reducedDur * actsCharge * (1f - timeUnder20)) / LatentGCD);
                TimeLostGCDsU20 += Math.Min(NumGCDsU20, (BaseMoveDur * moveGCDs * timeUnder20) / LatentGCD);
                TimeLostGCDsU20 -= Math.Min(TimeLostGCDsU20, (reducedDur * actsCharge * timeUnder20) / LatentGCD);
                CH.numActivatesO20 = actsCharge * (1f - timeUnder20);
                CH.numActivatesU20 = actsCharge * timeUnder20;
                float actsHF = 0f;
                if (CH.allNumActivates > CH.ability.Activates) actsHF += (CH.allNumActivates - CH.ability.Activates);
                HF.numActivatesO20 += actsHF * (1f - timeUnder20);
                HF.numActivatesU20 += actsHF * timeUnder20;
                if (_needDisplayCalcs && moveGCDs > 0) {
                    GCDUsage += string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Lost to Movement\n",
                        moveGCDs, moveGCDs * (1f - timeUnder20), moveGCDs * timeUnder20, BaseMoveDur, moveGCDs * BaseMoveDur);
                    GCDUsage += (CH.allNumActivates > 0 ?
                        string.Format("{0:000.000}=({1:000.000}+{2:000.000})@{3:0.000}s={4:000.000}s : Recovered by {5}\n",
                            CH.allNumActivates, // Total Acts
                            CH.numActivatesO20, // Acts Over 20
                            CH.numActivatesU20, // Acts Under 20
                            BaseMoveDur - reducedDur, // Amount Recovered Per
                            CH.allNumActivates * (BaseMoveDur - reducedDur), // Total Amount Recovered
                            CH.ability.Name) // The Name
                        : "");
                    GCDUsage += (actsHF > 0 ? actsHF.ToString("000.00") + " activates of " + HF.ability.Name + " to refresh " + CH.ability.Name : "");
                }
                percTimeInMovement = timelostwhilemoving / FightDuration;
            }
            return percTimeInMovement;
        }
#endif
        #endregion
        #endregion

        #region Cached Special Effects
        #endregion
    }
}
